今天如果你以快照(snapshot)保留一個值,這樣是 closure 嗎?
No, Closure 和值
並沒有關係 。
Closure 抓住(close over) 的是一個變數
,或是更明確的說,抓住整個 scope-based 裡面的那一個 identifer
。
這邊是一個具體的例子,你可以先看 line 3
可以想像, myTeacher 這時候 抓住(close over)整個 function object(code)
。
假設在 line 6
: myTeacher();
,這時候會印出 "Kyle",
因為 closure 是抓住整個 lexical scope ,代表往上層找
這個 lexical scope 的特性依然還是存在,所以此時印出 "Kyle"。
同理,如果把 teacher = "Suzy"
,此時往外找的對象值被改變了,line 9 就會印出 "Suzy" 。
這邊不是要強調 setTimeout ,而是昨天說 setTimeout 裡面的函式是運用到 Closure 機制
,所以可以使用不會報錯。
但是,剛剛有提到 Closure 不能
快照(snapshot),
所以當 setTimeout 可以執行的時候,
for 迴圈的 i 已經被更新過了,也就是前一個 teacher = "Suzy"
的例子。
這邊印出來的值都會是 i 被改變之後
的值 4
。
參考 setTimeout :
[day22] YDKJS (Closure) : Closure 入門: persistent lexical scope referenced data這邊先不解釋 setTimeout 的運作機制,
你可以想像 JavaScrip 引擎委託 網頁瀏覽器 存放一這個 waitASec(){...} 並且設定時間 100 毫秒後,再排隊執行(要排隊等全部 JS 執行後才能輪到 waitASec(){...} )。
前一個例子跑 for loop
的時候,只有一個變數
可以存 console.log 的值,同時
還要存 for loop 的條件,
所以 for loop 的條件會一直覆蓋
console.log 所需要的值。
那就換個想法,給兩個變數
存值。
這邊細部拆分:
很簡單的想法,我們在 closure 的內部存一個新
變數,
這樣每次迴圈都會有一個 新的 let j
, 我們就改成 console.log(j) 的值。
每一次都會產生一個新的 let j
,所以其實背後真實的感覺是
// j : 1
// j' : 2
// j'' : 3
let
之後這時候你應該知道,這三個 i 其實是不一樣
的 i 了,
因為 Closure 不能快照(snapshot)一個 value,我們就只能存在多個變數
裡面放值。
// i : 1
// i' : 2
// i'' : 3
昨天的結尾,有提到 說到這邊,484 覺得這個隔離的感覺,有一種 private, 有一種內聚力(Cohesion)很高的味道?
沒錯!
開始說 Modules 以前,我們先說 什麼不是 Modules
:
描述一下上面的 code 做什麼事 :
簡單來說,
其實是做一個 object,讓 namespace 不要污染到外面,
非官方的說:這只是 namespace pattern。
不是
?封裝(Encapsulation)
的概念。能不能把你之前做的事情,加上 隱藏資料和行為的細節
,
讓別人不能隨意取用你的資料?
註:
封裝(Encapsulation) 是很常見的軟體工程用字,表示能夠隱藏資料和行為的細節
。
也就是大家常見 public, private 的字眼。
可以用一個 function 回傳另一個包裝好的 object ,
這是我們的老套路。
但我們以前有提過一個可以創造新的 lexical scope 的方法 : IIFE
。
一次
這個例子之中,你可以用 workshop.ask 去存取 closure 著的 function (有被 return),
但是不能用 workshop.teacher 去存取 teacher 的值,
也就是成功把 teacher 隱藏
起來了。
這也是我們用 Module Pattern 很重要的原因,
狀態不要被其他程式碼更動
,可以追蹤(debug)。封裝(Encapsulation) 這個概念也符合之前說的最小權限原則
。
參考: [day20] YDKJS (Scope) : Advanced Scope
如果你讀資工或相關書籍,會有讀過 最小權限原則(Principle of least privilege),
你應該預設把值隱藏起來(keeping everying private),只把最少量真正需要的訊息公開。如果你按照最小權限原則,你可以處理掉三個問題:
- 變數命名重複問題 (naming collision problem)。
- 別人不會意外改到你的 code,也不能隨意存取你寫好的東西。
- Kyle 認為最重要的: 你可以保護你未來 重構(refactoring) , code 不會因為被其他人使用而不能改動。
當然,除了 IIFE 之外,我們還是可以用之前回傳 funtion object(code) 的方式來做 Moduel。
差別是, IIFE 是單例,也就是大家使用方法都是同一個實例。
用 function 會是儲存在一個變數上,可以產生很多個變數,
也就是多例, a.k.a Factory Pattern
。
這些習慣用某些方法或是語言機制(比如 closure),
實作做出來的東西,並不是一個真正在 JavaScript 內規範的東西。
會提到這個,是因為在 ES6,JavaScript 內規範一個相似的新東西 :
Kyle 認為,現在使用 ES6 Modules 要很謹慎,
因為 ES6 Modules 出來之後,很多以前存在的規範,或是根據不同執行環境產生的結果會有所不同,
可能結果並不相容
(主要是 Node.js 上使用 ES6 Modules 的相容問題 )。
Kyle 傾向用工具轉成(transpile) ES6 Modules 的語法,
至少在確定 node 整合完成以前 Kyle 都是這樣。
如果你想知道更多 :
node 想整合舊 Common JS module 和新 ES6 Modules 共用,產生一些還需要解決的問題,
比如很多 Corner case 還要處理,例如:循環引用。
其他風格的 import style :
把 import 視為 「import 一個 identifier」,
import 到現有 NameSpace(line 7 語法) 或是 Scope(line 1 語法) 內。
Kyle 是使用 UMD style modules (universal module definition)
舊的手動創造 Module Pattern
心得 :
從一開始的 lexical scope 到 Closure,
從 Closure 到 Modules 其實都還是Scope.
希望大家讀 YDKJS scope & closures,這幾天的文章可以有所幫助。